package fr.insalyon.sims.broadcast.network;

import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import javax.swing.event.EventListenerList;

/**
 * This class is a stream transmitter. 
 * It also implements DataListener which gets notified when it has data to transmit. 
 * 
 * @author GONZALEZ Silvia, GOTTERO Jeremie, SATTAYAKAWEE Nutchanat
 *
 */
abstract public class BaseStreamTransmitter implements IStreamTransmitter {
	// ------------------------------------------------------- CONSTANTS -- //
	// ------------------------------------------------------ ATTRIBUTES -- //
	
	/**
	 * list of DataErrorListener
	 */
	private EventListenerList listenersList;
	
	/**
	 * thread for transmitting the data
	 */
	private Thread streamTransmitterThread;
	
	/**
	 * queue of data to be transmitted
	 */
	private List<DataEvent> eventsQueue;
	
	// ---------------------------------------------------- CONSTRUCTORS -- //
	
	public BaseStreamTransmitter() {
		this.listenersList = new EventListenerList();
		this.eventsQueue = Collections.synchronizedList(
				new LinkedList<DataEvent>());
	}
	
	// --------------------------------------------------------- METHODS -- //
	/**
	 * Starts streamTransmitterThread
	 */
	public synchronized void start() {
		if (streamTransmitterThread == null) {
			streamTransmitterThread = new Thread() {
				public void run() {
					try {
						BaseStreamTransmitter.this.init();
						
						while (!isInterrupted()) {
							while (eventsQueue.size() > 0) {
								DataEvent e = eventsQueue.remove(0);
								writeStream(e.getData(),
										e.getOffset(), e.getLength());
							}
							
							if (!isInterrupted()) {
								try {
									sleep(10);
								} catch (Exception e) {
									// Ignored
								}
							}
						}
					} catch (Exception e) {
						fireStreamError(e);
					}
				}
			};
			streamTransmitterThread.setName(getClass().getSimpleName() + "Thread");
			streamTransmitterThread.start();
		}
	}

	/**
	 * Stops streamTransmitterThread
	 */
	public synchronized void stop() {
		if (streamTransmitterThread != null) {
			streamTransmitterThread.interrupt();
			synchronized (streamTransmitterThread) {
				streamTransmitterThread.notify();
			}
			eventsQueue.clear();
			streamTransmitterThread = null;
		}
	}

	/**
	 * Sends data
	 * 
	 * @param data data to be sent
	 * @param offset data offset
	 * @param length data length
	 */
	public void send(byte[] data, int offset, int length) {
		if (streamTransmitterThread != null) {
			eventsQueue.add(new DataEvent(null, data, offset, length));
		}
	}
	
	/**
	 * Adds DataErrorListener to listenersList
	 * 
	 * @param listener listener to be added
	 */
	public void addDataErrorListener(DataErrorListener listener) {
		listenersList.add(DataErrorListener.class, listener);
	}
	
	/**
	 * Removes DataErrorListener from listenersList. It does nothing when listener doesn't exist.
	 * 
	 * @param listener listener to be removed
	 */
	public void removeDataErrorListener(DataErrorListener listener) {
		listenersList.remove(DataErrorListener.class, listener);
	}
	
	// ------------------------------------------------- PRIVATE METHODS -- //
	/**
	 * Initializes the running streamReceiverThread
	 */
	abstract protected void init() throws IOException;

	/**
	 * Writes stream
	 * @param data data to be written
	 * @param offset data offset
	 * @param length data length
	 * @throws IOException if an I/O error occurs.
	 */
	abstract protected void writeStream(byte[] data, int offset, int length)
		throws IOException;

	/**
	 * Cleans all resources used in streamReceiverThread
	 * @throws IOException
	 */
	abstract protected void cleanUp() throws IOException;

	/**
	 * Sends error to all DataErrorListeners
	 * @param e error
	 */
	protected void fireStreamError(Exception e) {
		Object[] listeners = listenersList.getListenerList();
		
		for (int i = listeners.length - 2; i >= 0; i -= 2)
			if (listeners[i] == DataErrorListener.class)
				((DataErrorListener) listeners[i + 1]).streamError(
						new DataErrorEvent(this, e));
	}
	
	// --------------------------------------------------- INNER CLASSES -- //
}
